import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.TreeSet;

import Exceptions.ErrorReporter;
import Exceptions.ExpressionError;
import OptionsParser.*;

public class DefinesParser {
	public static final String packageName="DefParser";
	public static final String version="1.1";

	public static void main(String[] args) throws UnexpectedOption {
		OptionsParser o=new OptionsParser();
		try
		{
			o.AddOption("h,help","Display this help message.");
			o.AddOption("o,output=s","Write output to specified file.");
			o.AddOption("t,target","Generate GNU Make target.");
			o.AddOption("u,undef","Generate an undefines file.");
			o.AddOption("v,version","Show version information.");
			args=o.Parse(args);
		}
		catch(Exception e)
		{
			System.err.println("Error: "+e.getMessage());
			System.exit(1);
		}

		if(o.Get("help").matched)
		{
			System.out.println(packageName+" version "+version+".");
			System.out.println("");
			System.out.println("Usage: java DefinesParser.jar [OPTION]... [FILE]...");
			System.out.println("");
			System.out.println("Generate a verilog include file from a CSV variable");
			System.out.println("definitions file.");
			System.out.println("");
			System.out.println(o.ToString());
			System.out.println("If supplied, input is read from the listed files.");
			System.out.println("By default, input is taken from stdin and output");
			System.out.println("is written to stdout.");
			System.out.println("");
			System.out.println("Written by Adam Shapiro <ams348@cornell.edu>.");
			System.exit(0);
		}
		else if(o.Get("version").matched)
		{
			System.out.println(packageName+" version "+version+".");
			System.out.println("Written by Adam Shapiro.");
			System.out.println("Report bugs to ams348@cornell.edu.");
			System.exit(0);
		}

		//If no input files specified, default to stdin.
		if(args.length==0)
		{
			args=new String[1];
			args[0]="-";
		}

		//Process files.
		String output;
		if(o.Get("target").matched)
		{
			output=GenerateMakeTarget(args,(o.Get("output").matched ? o.Get("output").stringValue : null));
		}
		else
		{
			output=GenerateDefinesFile(args, o.Get("undef").matched);
		}
		
		//Print error report.
		System.err.print(ErrorReporter.Report());
		System.err.print(ErrorReporter.Summary());
		if(ErrorReporter.ReportSize()>0)
		{
			System.exit(1);
		}

		//Print to output file if completed successfully.
		//If no output file specified, print to stdout.
		if(!o.Get("output").matched)System.out.print(output);
		else
		{
			try
			{
				PrintStream outWriter=new PrintStream(new FileOutputStream(o.Get("output").stringValue));
				outWriter.print(output);
			}
			catch(Exception e)
			{
				System.err.println("Error: unable to open output file '"+o.Get("output").stringValue+"'.");
			}
		}
	}

	public static String GenerateMakeTarget(String [] inputFiles, String outputFile)
	{
		String output="";
		
		//Generate one target per input file.
		for(String file : inputFiles)
		{
			InputParser in=new InputParser(file);
			ArrayList<String> deps=in.ListDependencies();

			if(ErrorReporter.ErrorCount()>0)return "";

			output+=file;
			if(outputFile!=null)output+=" "+outputFile;
			output+=":";
			for(String dep : deps)output+=" "+dep;
			output+="\n";
		}
		
		return output;
	}
	
	public static String GenerateDefinesFile(String [] inputFiles, boolean printUndef)
	{
		//Parse input files.
		HashMap<String,MacroEntry> vars=new HashMap<String,MacroEntry>();
		ArrayList<String> verilog=new ArrayList<String>();
		for(String file : inputFiles)
		{
			InputParser in=new InputParser(file);
			in.Parse(vars,verilog);
		}

		//Add constants.
		MacroEntry constantEntry=new MacroEntry();
		constantEntry.print=false;
		constantEntry.file="";
		constantEntry.line=-1;
		constantEntry.comments=packageName+" constant.";
		try
		{
			constantEntry.expression=new Expression("3.141592654");
			vars.put("PI",constantEntry);
		}
		catch(Exceptions.ParserError e){}

		//Print output file.
		if(ErrorReporter.ErrorCount()==0)
		{
			String output;
			String date=DateFormat.getDateTimeInstance().format(new Date());
			output="//Generated by "+packageName+" v"+version+" on "+date+".\n";
			output+="//This file has been automatically generated.\n";
			output+="//Edit contents with extreme caution.\n\n";

			if(!printUndef && verilog.size()>0)
			{
				for(String s : verilog)
				{
					output+=s+"\n";
				}
				output+="\n";
			}

			HashMap<String,Expression> expList=new HashMap<String,Expression>();
			for(String key : vars.keySet())
			{
				expList.put(key,vars.get(key).expression);
			}

			for(String variable : (new TreeSet<String>(vars.keySet())))
			{
				MacroEntry entry=vars.get(variable);

				if(printUndef)
				{
					output+="`ifdef "+variable+"\n";
					output+=" `undef "+variable+"\n";
					output+="`endif\n";
					output+="\n";
				}
				else
				{
					if(!entry.print)continue;
					try
					{
						if(!entry.comments.equals(""))
						{
							output+="//";
							output+=entry.comments.replaceAll("\\n","\n//");
							output+="\n";
						}
						output+="`ifndef "+variable+"\n";
						output+=" `define "+variable+" "+entry.expression.Value(expList)+"\n";
						output+="`endif\n";
						output+="\n";
					}
					catch(ExpressionError e)
					{
						e.SetVariable(variable);
						ErrorReporter.Error(e.getMessage());
					}
				}
			}
			
			return output;
		}
		else return "";
	}
}
